package com.reviewcode.cachemicroservice.common.bean;

import org.springframework.stereotype.Component;

import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;

/**
 * @author vcorazon
 * @type Class
 * @description TODO
 * @since 2021/07/22 22:01
 */
public class MicroCache<K, V> {
    private final ConcurrentHashMap<K, V> cacheMap;
    private final ConcurrentHashMap<K, Long> expireTimeMap;
    //设置线程安全的优先队列加快过期键值对的清除
    private final PriorityBlockingQueue<K> priorityQueue;
    //    private ScheduledExecutorService executorService;
    private final int threshold;
    private final int maxCapacity;

    public MicroCache(int maxCapacity, int threshold) {
        cacheMap = new ConcurrentHashMap<>(maxCapacity);
        expireTimeMap = new ConcurrentHashMap<>(maxCapacity);
        priorityQueue = new PriorityBlockingQueue<>(maxCapacity, (o1, o2) -> (int) (expireTimeMap.get(o1) - expireTimeMap.get(o2)));
        this.threshold = threshold;
        this.maxCapacity = maxCapacity;
    }

    public V put(K key, V value, long expiretime) {
        cacheMap.put(key, value);
        expireTimeMap.put(key, System.currentTimeMillis() + expiretime);
        priorityQueue.offer(key);
        //queue长度大于设定阈值后开始按一定概率清除过期键值
        if (priorityQueue.size() > threshold) {
            //主动过期检测
            expiredCheck();
        }
        return value;
    }

    public V get(K key) {
        //首先判断是否过期(惰性过期检测)
        if (expireTimeMap.containsKey(key) && expireTimeMap.get(key) < System.currentTimeMillis()) {
            remove(key);
            return null;
        }
        return cacheMap.get(key);
    }

    public int size() {
        return cacheMap.size();
    }

    public void expiredCheck() {
        //概率检测, 键值对越多，put时进入过期检测的概率越大
        if ((size() / maxCapacity) > (new Random().nextInt(1))) {
            //如果已经有键值对过期了，移除优先队列从头开始到第一个没到期的所有节点
            if (!priorityQueue.isEmpty() &&
                    (expireTimeMap.get(priorityQueue.peek()) < System.currentTimeMillis())) {
                removeExpiredItem();
            }
        }
    }

    //sychronized保证expirationExcutor线程与put线程中的检测操作有序进行
    public synchronized void removeExpiredItem() {
        while (!priorityQueue.isEmpty()) {
            if (expireTimeMap.get(priorityQueue.peek()) > System.currentTimeMillis()) {
                break;
            }
            K key = priorityQueue.poll();
            //对已经过期的键值对删除
            remove(key);
        }
    }

    private void remove(K key) {
        cacheMap.remove(key);
        expireTimeMap.remove(key);
    }
}
